﻿using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace AIthin.Neuro
{

    [Serializable]
    public class Neuron : INeuron
    {
        public float X { get; internal set; }
        public float Y { get; internal set; }
        public float Z { get; internal set; }
        /// <summary>
        /// ident number
        /// </summary>
        public int ID { get; internal set; }
        public int LifeTime { get; protected set; }
        public bool Prozessed { get; protected set; }
        public bool Activated { get; protected set; }
        /// <summary>
        /// Neurons list for all neurons (static)
        /// </summary>
        public static List<int> Neurons = new List<int>();
        /// <summary>
        /// Neuron's inputs count.
        /// </summary>
        public int InputsCount { get; protected set; }
        /// <summary>
        /// Neuron's inputs count.
        /// </summary>
        int OutputsCount { get; set; }
        float threshold { get; set; }
        Dendrit[] inputs;
        public AxonTerminal[] outputs;
        bool AllInputsActivated = false;
        int DurationBeActiv = 3;

        public Neuron(int Ident, int inputsCount, int outputsCount)
        {
            ID = Ident;
            Neurons.Add(ID);
            Prozessed = false;
            Activated = false;
            LifeTime = 0;
            InputsCount = inputsCount;
            OutputsCount = outputsCount;
            inputs = new Dendrit[inputsCount];
            outputs = new AxonTerminal[outputsCount];
            for (int i = 0; i < inputsCount; i++) inputs[i] = new Dendrit();
            for (int i = 0; i < outputsCount; i++) outputs[i] = new AxonTerminal();
            threshold = Convert.ToSingle(0.3f * InputsCount);
            // threshold = 1.5f;
            Debug.WriteLine(ID, "neuron created");
        }
        /// <summary>
        /// returns connected neurons numbers or IDs
        /// </summary>
        /// <param name="inputsOut">input neurons IDs</param>
        /// <param name="outputsOut">outputs neurons IDs</param>
        public void getConnections(out int[] inputsOut, out int[] outputsOut)
        {
            inputsOut = new int[InputsCount];
            outputsOut = new int[OutputsCount];
            int i = 0;
            int j = 0;
            foreach (Dendrit d in inputs) inputsOut[i++] = d.ConnectedNeuron;
            foreach (AxonTerminal a in outputs) outputsOut[j++] = a.ConnectedNeuron;
        }
        public bool ConnectToOutputOf(ref Neuron NeuronToConnect)
        {
            // check inputscount
            // check if that neuron is already connected
            foreach (Dendrit d in inputs)
            {
                if (d.ConnectedNeuron == NeuronToConnect.ID)
                {
                    Debug.WriteLine("neuron " + this.ID.ToString() + " double connection to: " + NeuronToConnect.ID.ToString());
                    return false;
                }
            }

            for (int i = 0; i < InputsCount; i++)
            {
                // find free input
                if (inputs[i].ConnectedNeuron == 0)
                {
                    for (int j = 0; j < NeuronToConnect.OutputsCount; j++)
                    {
                        //find free output
                        if (NeuronToConnect.outputs[j].ConnectedNeuron == 0)
                        {

                            NeuronToConnect.outputs[j].ConnectedNeuron = this.ID;
                            NeuronToConnect.outputs[j].ConnectedDendrit = i;
                            inputs[i].ConnectedNeuron = NeuronToConnect.ID;
                            inputs[i].ConnectedAxon = j;
                            //
                            //Debug.WriteLine("neuron " + this.ID.ToString() + " dendrit " + i.ToString()
                            //    + " to: " + NeuronToConnect.ID.ToString() + " axon " + j.ToString());
                            return true;
                        }
                    }
                    //TODO  All outputs are already connected
                    Debug.WriteLine(this.ID, "!!! no free axons for");
                    return false;
                }
            }
            //TODO  All inputs are already connected
            Debug.WriteLine(this.ID, "!!! no free dendrits for");
            return false;
        }
        public void AddOutput()
        {
            OutputsCount++;
            //TODO to slow
            AxonTerminal[] newOutputs = new AxonTerminal[OutputsCount];
            outputs.CopyTo(newOutputs, 0);
            outputs = newOutputs;
            outputs[OutputsCount - 1] = new AxonTerminal();
            //TODO change
            threshold = Convert.ToSingle(0.7f * InputsCount);
            return;
        }
        public bool Computate()
        {

            //TODO when return, output signal will be twise computed, resourses
            if (Prozessed)
            {
                Debug.WriteLine(this.ID, "this neuron was processed");
                return true;
            }
            //TODO Check for Ticks and unset Prozessed for a dendrites
            if (!AllInputsActivated)
            {
                Debug.WriteLine(this.ID, "not all inputs active for neuron ");
                return false;
            }

            //activate inertion
            if (DurationBeActiv-- > 0)
                Activated = true;
            else
                Activated = false;

            // when neuron is activated don't need it compute

            float inputSignal = 0f;
            float outputSignal = 0f;
            // sigmoid function
            foreach (Dendrit dendrit in inputs) inputSignal += dendrit.Out();
            if (inputSignal > threshold)
            {
                outputSignal = 1.0f;
                Activated = true;
                DurationBeActiv = 3;
            }
            LifeTime += 1;
            //TODO overwriting signals for the outputs neurons replace with learn algoritm
            // set outputs
            foreach (AxonTerminal axon in outputs) axon.Signal = outputSignal;
            // neuron is activated
            Prozessed = true;
            //Debug.Write(this.ID, "neuron ");
            //Debug.WriteLine(outputSignal, " was processed and get signal");
            return true;
        }
        /// <summary>
        /// activate input and transfer signal to processing
        /// </summary>
        /// <param name="input">number(index) of input of this neuron</param>
        /// <param name="signal">input signal value</param>
        public void ActivateInput(int input, float signal)
        {
            inputs[input].Prozessed = true;
            inputs[input].Signal = signal;
            foreach (Dendrit dendrit in inputs) if (!dendrit.Prozessed) return;
            AllInputsActivated = true;
        }
        /// <summary>
        /// Set inputsignal for neuron 
        /// (used for retina neurons)
        /// </summary>
        /// <param name="inputSignal">value 1-black, 0-white</param>
        public void SetInput(short inputSignal)
        {
            //TODO Make input as float
            foreach (Dendrit d in inputs)
            {
                d.Signal = Convert.ToSingle(inputSignal);
                d.Prozessed = true;
            }
            this.AllInputsActivated = true;
        }
        /// <summary>
        /// set or analyse output signal for last(end) neurons
        /// </summary>
        /// <param name="outputSignal">value of output 1-256</param>
        public void SetOutput(short outputSignal)
        {
            this.Prozessed = true;
            this.DurationBeActiv = 3;
        }
        /// <summary>
        /// neuron must be reset after wave or tick for network was computed
        /// </summary>
        public void Reset()
        {
            this.Prozessed = false;
            this.AllInputsActivated = false;
            Activated = false;
            // inputs reset
            foreach (Dendrit d in inputs) d.Reset();
            //outputs reset
            foreach (AxonTerminal a in outputs) a.Reset();
            //Debug.WriteLine(this.ID, "reset neuron for next tick");
        }

    }
}
